package com.shortlink.server.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shortlink.common.exception.ClientException;
import com.shortlink.common.exception.ServiceException;
import com.shortlink.server.dao.entity.ShortLinkMapperDO;
import com.shortlink.server.dao.mapper.ShortLinkMapperMapper;
import com.shortlink.server.dto.shortlinkmapper.ShortLinkMapperInsertReqDTO;
import com.shortlink.server.dto.shortlinkmapper.ShortLinkMapperUpdateReqDTO;
import com.shortlink.server.dto.shortlinkmapper.ShortLinkReqDTO;
import com.shortlink.server.service.ShortLinkMapperService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.shortlink.common.base.errcode.BaseErrorCode.FLOW_LIMIT_ERROR;
import static com.shortlink.common.constant.RedisKeyConstant.*;

/**
 * 短链接映射接口实现层
 *
 * @author wp
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ShortLinkMapperServiceImpl extends ServiceImpl<ShortLinkMapperMapper, ShortLinkMapperDO> implements ShortLinkMapperService {
    private final RedissonClient redissonClient;
    private final StringRedisTemplate stringRedisTemplate;

    @Override
    public void signalInsert(ShortLinkMapperInsertReqDTO shortLinkMapperInsert) {
        if (BeanUtil.isEmpty(shortLinkMapperInsert)) {
            log.info(String.format("shortLinkMapperInsert(%s) 属性不能为空", shortLinkMapperInsert.toString()));
            throw new ClientException("参数错误");
        }
        ShortLinkMapperDO shortLinkMapper = BeanUtil.copyProperties(shortLinkMapperInsert, ShortLinkMapperDO.class);
        boolean succeed = save(shortLinkMapper);
        if (!succeed) {
            throw new ServiceException("短链接映射插入失败");
        }
    }

    @Override
    public void batchInsert(List<ShortLinkMapperInsertReqDTO> shortLinkMapperInserts) {
        if (ObjectUtil.isEmpty(shortLinkMapperInserts)) {
            log.info(String.format("shortLinkMapperInserts(%s) 不能为空", shortLinkMapperInserts));
            throw new ClientException("参数错误");
        }
        List<ShortLinkMapperDO> shortLinkMappers = shortLinkMapperInserts.stream()
                .map(shortLinkMapperInsert -> {
                    if (BeanUtil.isEmpty(shortLinkMapperInsert)) {
                        log.info(String.format("shortLinkMapperInserts(%s)", shortLinkMapperInserts));
                        log.info(String.format("shortLinkMapperInsert(%s) 属性不能为空", shortLinkMapperInsert.toString()));
                        throw new ClientException("参数错误");
                    }
                    return BeanUtil.copyProperties(shortLinkMapperInsert, ShortLinkMapperDO.class);
                })
                .toList();
        boolean succeed = saveBatch(shortLinkMappers);
        if (!succeed) {
            throw new ServiceException("短链接映射批量插入失败");
        }
    }

    @Override
    public void updateByShortUri(ShortLinkMapperUpdateReqDTO shortLinkMapperUpdate) {
        Long shortLinkId = shortLinkMapperUpdate.getShortLinkId();
        LocalDate validDate = shortLinkMapperUpdate.getValidDate();
        if (!ObjectUtil.isAllNotEmpty(shortLinkId, validDate)) {
            log.error(String.format("shortLinkId(%s) validDate(%s) 不能为空", shortLinkId, validDate));
            throw new ClientException("参数错误");
        }

        boolean succeed = lambdaUpdate()
                .eq(ShortLinkMapperDO::getId, shortLinkId)
                .set(ShortLinkMapperDO::getValidDate, validDate)
                .update();
        if (!succeed) {
            throw new ClientException("短链接映射更新失败（短链接不存在）");
        }
    }

    @Override
    public void moveToRecycleBin(Long id) {
        boolean succeed = lambdaUpdate()
                .eq(ShortLinkMapperDO::getId, id)
                .set(ShortLinkMapperDO::getDelFlag, 1)
                .update();
        if (!succeed) {
            throw new ClientException("短链接映射移入回收站失败（短链接不存在）");
        }
    }

    @Override
    public void recover(Long id) {
        boolean succeed = lambdaUpdate()
                .eq(ShortLinkMapperDO::getId, id)
                .set(ShortLinkMapperDO::getDelFlag, 0)
                .update();
        if (!succeed) {
            throw new ClientException("短链接映射恢复失败（短链接不存在）");
        }
    }

    @Override
    public String getOriginByShortLink(ShortLinkReqDTO shortLinkReq) {
        String domain = shortLinkReq.getDomain();
        String shortUri = shortLinkReq.getShortUri();
        if (!ObjectUtil.isAllNotEmpty(domain, shortUri)) {
            log.error(String.format("domain(%s) shortUri(%s) 不能为空", domain, shortUri));
            throw new ClientException("参数错误");
        }

        RLock lock = redissonClient.getLock(String.format(LOCK_SHORT_LINK_MAPPER_KEY, domain, shortUri));
        boolean succeed = lock.tryLock();
        if (succeed) {
            try {
                ShortLinkMapperDO shortLinkMapper = lambdaQuery()
                        .eq(ShortLinkMapperDO::getDomain, domain)
                        .eq(ShortLinkMapperDO::getShortUri, shortUri)
                        .eq(ShortLinkMapperDO::getDelFlag, ShortLinkMapperDO.DEL_FLAG_FAIL)
                        .one();
                if (shortLinkMapper == null ||
                        (shortLinkMapper.getValidDate() != null && shortLinkMapper.getValidDate().isBefore(LocalDate.now()))){
                    String nullRedisKey = String.format(NULL_SHORT_LINK_MAPPER_KEY, domain, shortUri);
                    stringRedisTemplate.opsForValue().set(nullRedisKey, NULL_SHORT_LINK_MAPPER_VALUE, NULL_SHORT_LINK_MAPPER_TTL, TimeUnit.MINUTES);
                    return null;
                }
                String redisKey = String.format(SHORT_LINK_MAPPER_KEY, domain, shortUri);
                stringRedisTemplate.opsForValue().set(redisKey, shortLinkMapper.getOriginUrl(), SHORT_LINK_MAPPER_TTL, TimeUnit.MINUTES);
                return shortLinkMapper.getOriginUrl();
            } finally {
                lock.unlock();
            }
        } else {
            throw new  ServiceException(FLOW_LIMIT_ERROR);
        }
    }
}